/***************************************************************************
 *   Copyright © 2010 by Nikolay Kornaev                                   *
 *   n.kornaev@gmail.com                                                   *
 *                                                                         *
 *                    This file is part of neuma.                          *
 *                                                                         *
 *   neuma is free software; you can redistribute it and/or modify it      *
 *   under the terms of the GNU General Public License as published by     *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   neuma is distributed in the hope that it will be useful,              *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
 ***************************************************************************/

#include <QtCore>
#include <QtGui>
#include <QtSvg>

#include "BlockNeuma.h"
#include "DisplayWidget.h"
#include "Interface_SourceParser.h"
#include "MainWindow.h"
#include "SettingsHandler.h"
#include "SNPair.h"
#include "utils.h"

namespace Neuma
{
  void MainWindow::on_aRefresh_triggered()
  {
    widget.preview->updatePreview();
  }

  int MainWindow::exportImage(const QString &format)
  {
    QTime t;
    t.start();

    QString newPath = QFileDialog::getSaveFileName(this, tr("Экспорт в растровое изображение"),
        path, tr("Файлы %1 (*.%2)").arg(format.toUpper()).arg(format.toLower()));

    if (newPath.isEmpty())
      return 1;

    QApplication::setOverrideCursor(Qt::WaitCursor);
    int res = set->val(RendererExportResolution).toInt();
    qreal mult = printer.logicalDpiX() / res;

    // создаём пустое изображение. размер -- область печати принтера.
    QImage image = QImage((printer.paperSize(QPrinter::DevicePixel) / mult).toSize(),
        QImage::Format_ARGB32);

    // устанавливаем изображению разрешение принтера, приводя к `точкам на метр'
    image.setDotsPerMeterX((int) (res * 1000 / 25.4));
    image.setDotsPerMeterY((int) (res * 1000 / 25.4));

    // рисуем на этом изображении
    //    painter.scale(mult, mult);
    painter.begin(&image);
    draw(painter, true);
    painter.end();

    // сохраняем файл
    QImageWriter writer(newPath);
    bool ok;
    if (writer.canWrite())
      ok = writer.write(image);
    else
    {
      QApplication::restoreOverrideCursor();
      return 1;
    }
    newPath.chop(4);
    path = newPath;
    debug(QString("Time elapsed for exporting image %1: %2ms").arg(newPath) .arg(t.elapsed()));
    QApplication::restoreOverrideCursor();
    return !ok;
  }

  void MainWindow::draw(QPrinter *printer)
  {
    painter.begin(printer);
    draw(painter);
    painter.end();
  }

  void MainWindow::draw(QPainter &painter, bool isExport)
  {
    QTime t;
    t.start();

    /// я не уверен, что тут надо использовать: logicalDpiX или physicalDpiX
    /// делим на 25.4, чтобы получить мультипликатор перевода миллиметров в пикселы
    double dpMM = painter.device()->logicalDpiX() / 25.4;

    QRectF pageRect; /// область рисования, без полей

    /// если рисуем на экране или принтере
    if (!isExport)
      pageRect = painter.viewport();
    /// если рисуем на QImage или QSvgGenerator
    else
    {
      qreal mB = set->val(PageMarginBottom).toDouble() * dpMM;
      qreal mL = set->val(PageMarginLeft).toDouble() * dpMM;
      qreal mR = set->val(PageMarginRight).toDouble() * dpMM;
      qreal mT = set->val(PageMarginTop).toDouble() * dpMM;
      pageRect = QRectF(mL, mT, painter.viewport().width() - mR - mL, painter.viewport().height()
          - mT - mB);
    }

    painter.setRenderHint(QPainter::Antialiasing);
    /// заливаем фоном TODO: svn:issue14
    painter.setBrush(Qt::white);
    painter.drawRect(painter.viewport());

    /// если надо, рисуем обводку полосы
    if (set->val(RendererOutlinePage).toBool())
    {
      painter.save();
      painter.setPen(QColor(set->val(RendererOutlineColorPage).toString()));
      painter.drawRect(pageRect);
      painter.restore();
    }

    /// обновляем классовые переменные
    nFont = QFont(set->val(RendererFontNameNeuma).toString(),
        set->val(RendererFontSizeNeuma).toDouble());
    nColor = QColor(set->val(RendererFontColorNeuma).toString());
    mColor = QColor(set->val(RendererFontColorMarks).toString());

    QFont sFont(set->val(RendererFontNameSyllabe).toString(),
        set->val(RendererFontSizeSyllabe).toDouble());

    QFontMetricsF nFm(nFont, painter.device());
    QFontMetricsF sFm(sFont, painter.device());

    QColor sColor(set->val(RendererFontColorSyllabe).toString());

    QString text = teSource->toPlainText();
    if (!currentSP->fromText(text, data))
      return;

    /// если текст пустой, то больше делать нечего
    if (!data->size())
      return;

    double vOff = set->val(RendererSpaceBlocksVertical).toDouble() * dpMM;
    double hOff = set->val(RendererSpaceBlocksHorizontal).toDouble() * dpMM;
    double lSpc = set->val(RendererSpaceLines).toDouble() * dpMM;

    bool nos = set->val(RendererNeumaOverSyllabe).toBool(); /// флаг, учитывать ли гласную слога
    bool ntm = set->val(RendererNeumaTruncateMarks).toBool(); /// флаг, учитывать ли крайние пометы

    QPointF point(pageRect.topLeft());

    for (int i = 0; i < data->size(); ++i)
    {
      qreal y = point.y(); /// верх невм строки
      NSPair *pair = data->at(i); /// текущий блок (НС-пара)

      /// проверяем на перевод строки
      if (pair->isNewLine())
      {
        /// по горизонтали на левый край
        point.setX(pageRect.left());
        /// по вертикали к `y' прибавляем высоты невмы и слога и спейсеры
        point.setY(y + nFm.height() * N_FONT_HEIGHT_MULTIPLICATOR + vOff + sFm.height() + lSpc);
        /// проверяем на заползание за нижнюю границу
        if ((point.y() + nFm.height() + vOff + sFm.height()) > pageRect.bottom())
        {
          bool m = teSource->document()->isModified();
          data->insert(i + 1, new NSPair(tr(">> Следующий текст не помещается на странице. "
            "Увеличьте размер печатной области или создайте вторую страницу в новом документе.")));
          if (currentSP->toText(data, text))
            teSource->setPlainText(text);
          teSource->document()->setModified(m);

          debug(QString("Time elapsed for drawing on %1: %2ms").arg(isExport ? "image"
              : "screen or printer") .arg(t.elapsed()));
          return;
        }
        /// больше делать нечего, берём следующий блок
        continue;
      }
      /// проверяем на комментарий
      if (pair->isComment())
        continue;

      QString neuma = pair->getNeuma(); /// невма этого блока
      QString syllabe = pair->getSyllabe(); /// слог этого блока

      QSizeF nSize = nFm.size(Qt::TextSingleLine, neuma); /// размер невмы
      QSizeF sSize = sFm.size(Qt::TextSingleLine, syllabe); /// размер слога

      BlockNeuma bn(neuma, &nFm);

      qreal nW1, nW2, nW3;
      if (ntm)
      {
        nW1 = bn.getNW1();
        nW2 = bn.getNW2();
        nW3 = bn.getNW3();
      }
      else
      {
        nW1 = 0;
        nW2 = nSize.width();
        nW3 = 0;
      }

      //      qDebug() << neuma << "\tnW1" << nW1 << "\tnW2" << nW2 << "\tnW3" << nW3;
      qreal nH = nSize.height() * N_FONT_HEIGHT_MULTIPLICATOR; /// высота невмы
      qreal sW = sSize.width(); /// ширина слога
      qreal sW2 = syllabeCenterX(syllabe, sFm, nos); /// "половина" ширины слога
      qreal sH = sSize.height(); /// высота слога

      /// от левой границы пары до "якоря выравнивания"
      qreal untilAnchor = qMax(nW1 + nW2 / 2, sW2);
      /// общая ширина пары
      qreal pairWidth = untilAnchor + qMax(nW2 / 2 + nW3, sW - sW2);

      /// проверяем, не выходим ли за правую границу
      if ((point.x() + pairWidth) > pageRect.right())
      {
        /// по горизонтали на левый край
        point.setX(pageRect.left());
        /// по вертикали к `y' прибавляем высоты невмы и слога и спейсеры
        point.setY(y + nH + vOff + sH + lSpc);
        /// обновляем `y'
        y = point.y();
      }

      /// прямоугольник невмы
      QRectF nRect(point.x() + untilAnchor - (nW1 + nW2 / 2), point.y(), nW1 + nW2 + nW3, nH);

      /// если надо, рисуем обводку невмы
      if (set->val(RendererOutlineNeuma).toBool())
      {
        painter.save();
        painter.setPen(QColor(set->val(RendererOutlineColorNeuma).toString()));
        painter.drawRect(nRect);
        qreal x = nRect.left() + nW1 + nW2 / 2;
        painter.drawLine(x, point.y(), x, point.y() + nH);
        painter.restore();
      }

      /// рисуем невму
      painter.save();
      drawNeuma(painter, bn, nRect);
      painter.restore();

      /// сдвигаемся вниз
      point.setY(y + nH + vOff);
      QRectF sRect(point.x() + untilAnchor - sW2, point.y(), sW, sH); /// прямоугольник слога

      /// если надо, рисуем обводку слога
      if (set->val(RendererOutlineSyllabe).toBool())
      {
        painter.save();
        painter.setPen(QColor(set->val(RendererOutlineColorSyllabe).toString()));
        painter.drawRect(sRect);
        qreal x = sRect.left() + sW2;
        painter.drawLine(x, point.y(), x, point.y() + sH);
        painter.restore();
      }

      ///рисуем слог
      painter.setPen(sColor);
      painter.setFont(sFont);
      painter.drawText(sRect, Qt::AlignHCenter, syllabe);

      /// по вертикали возвращаемся на верхнюю линию строки блоков
      point.setY(y);
      /// по горизонтали смещаемся вправо
      point.setX(point.x() + pairWidth + hOff);
    }

    bool m = teSource->document()->isModified();
    if (currentSP->toText(data, text))
      teSource->setPlainText(text);
    teSource->document()->setModified(m);
    setWindowModified(m);

    debug(QString("Time elapsed for drawing on %1: %2ms").arg(isExport ? "image"
        : "screen or printer") .arg(t.elapsed()));
  }

  void MainWindow::drawNeuma(QPainter &painter, const BlockNeuma &neuma, const QRectF &nRect)
  {
    //    qDebug() << __PRETTY_FUNCTION__;
    QFontMetricsF nFm(nFont, painter.device());
    painter.setFont(nFont);
    qreal hOffset = nRect.left();
    if (neuma.getPartsCount())
    {
      ushort i = 0;
      do
      {
        QString part = neuma.getPartText(i);
        bool isMark = neuma.getPartIsMark(i);
        if (isMark)
          painter.setPen(mColor);
        else painter.setPen(nColor);
        painter.drawText(
            QPointF(hOffset, nRect.top() + nFm.ascent() * N_FONT_HEIGHT_MULTIPLICATOR), part);
        //	    qDebug() << i << "of" << neuma.getPartsCount() << "painted" << part;
        hOffset += neuma.getPartWidth(i);
        i++;
      }
      while (i < neuma.getPartsCount());
    }
  }

  qreal MainWindow::syllabeCenterX(const QString &syllabe, const QFontMetricsF &fm,
      const bool overVocal)
  {
    if (!overVocal)
      return fm.width(syllabe) / 2;

    QRegExp
        rxVocal =
            QRegExp(
                "[0ABEHIJKMNOQSTUWYZaehijkmnoqstuvwyz{|}±µЁЂЃІЇЈЉЊЋЌЎЏАЕИОУЫЭЮЯаеиоуыэюяёђѓєіїјљњћќўџҐґ†‡‰›]");

    int vocalPosition = rxVocal.indexIn(syllabe);
    if (vocalPosition == -1)
    {
      rxVocal = QRegExp("[Йй]");
      vocalPosition = rxVocal.indexIn(syllabe);
      if (vocalPosition == -1)
      {
        rxVocal = QRegExp("-");
        vocalPosition = rxVocal.indexIn(syllabe);
      }
    }
    if (vocalPosition == -1)
      return 0; /// гласная не найдена

    /*    debug(
     QString("Vocal %1 found after %2 (returned %3px)").arg(syllabe.mid(vocalPosition, 1)).arg(
     syllabe.left(vocalPosition)).arg(fm.width(syllabe.left(vocalPosition - 1)) + fm.width(
     syllabe.mid(vocalPosition, 1)) / 2));
     */
    return fm.width(syllabe.left(vocalPosition)) + fm.width(syllabe.mid(vocalPosition, 1)) / 2;
  }

  void MainWindow::processExport()
  {
    QString format;
    QAction *a = qobject_cast<QAction *> (sender());
    /// наверное, тут лучше бы использовать data()
    if (a == aExportBMP)
      format = "bmp";
    else if (a == aExportJPG)
      format = "jpg";
    else if (a == aExportPNG)
      format = "png";
    else if (a == aExportTIF)
      format = "tif";
    else return;

    exportImage(format);
  }

  void MainWindow::on_aExportSVG_triggered()
  {
    QString newPath = QFileDialog::getSaveFileName(this, tr("Экспорт в векторное изображение"),
        path, tr("Файлы Scallable Vector Graphics (*.svg)"));

    if (newPath.isEmpty())
      return;

    QSvgGenerator generator;
    generator.setFileName(newPath);
    int res = set->val(RendererExportResolution).toInt();
    qreal mult = printer.logicalDpiX() / res;
    generator.setSize((printer.paperSize(QPrinter::DevicePixel) / mult).toSize());
    generator.setResolution(res);
    //    QRectF pr(printer.paperRect(QPrinter::DevicePixel));
    //    generator.setViewBox(QRectF(pr.topLeft() / mult, pr.bottomRight() / mult));

    generator.setTitle(tr("Neuma export"));
    generator.setDescription(tr("An SVG drawing created by the Neuma program"));

    painter.begin(&generator);
    draw(painter, true);
    painter.end();

    newPath.chop(4);
    path = newPath;
  }
}
